package main

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"

	"github.com/golang/protobuf/proto"

	"github.com/hyperledger/fabric/common/configtx"
	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
	"github.com/hyperledger/fabric/core/ledger/util"
	"github.com/hyperledger/fabric/protos/common"
	"github.com/hyperledger/fabric/protos/msp"
	"github.com/hyperledger/fabric/protos/peer"
	"github.com/hyperledger/fabric/protos/utils"
)

func main() {
	blockBuf, err := ioutil.ReadFile("block_200")
	if err != nil {
		fmt.Println(err)
		return
	}
	block := new(common.Block)
	err = proto.Unmarshal(blockBuf, block)
	if err != nil {
		fmt.Println(err)
		return
	}
	parseBlock(block)
}

func parseBlock(block *common.Block) error {
	var err error

	// Handle header
	fmt.Printf("Block: Number=[%d], CurrentBlockHash=[%s], PreviousBlockHash=[%s]\n",
		block.GetHeader().Number,
		base64.StdEncoding.EncodeToString(block.GetHeader().DataHash),
		base64.StdEncoding.EncodeToString(block.GetHeader().PreviousHash))

	// Handle transaction
	var tranNo int64 = -1
	txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
	if len(txsFilter) == 0 {
		txsFilter = util.NewTxValidationFlags(len(block.Data.Data))
		block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter
	}

	for _, envBytes := range block.Data.Data {
		tranNo++
		if txsFilter.IsInvalid(int(tranNo)) {
			fmt.Printf("    Transaction: No=[%d], Status=[INVALID]\n", tranNo)
			continue
		} else {
			fmt.Printf("    Transaction: No=[%d], Status=[VALID]\n", tranNo)
		}

		var env *common.Envelope
		if env, err = utils.GetEnvelopeFromBlock(envBytes); err != nil {
			return err
		}

		var payload *common.Payload
		if payload, err = utils.GetPayload(env); err != nil {
			return err
		}

		var chdr *common.ChannelHeader
		if chdr, err = utils.UnmarshalChannelHeader(payload.Header.ChannelHeader); err != nil {
			return err
		}
		fmt.Printf("        txid=[%s], channel=[%s]\n", chdr.TxId, chdr.ChannelId)

		var shdr *common.SignatureHeader
		if shdr, err = utils.GetSignatureHeader(payload.Header.SignatureHeader); err != nil {
			return err
		}

		var mspid, subject string
		if mspid, subject, err = decodeSerializedIdentity(shdr.Creator); err != nil {
			return err
		}
		fmt.Printf("        creator=[%s:%s]\n", mspid, subject)

		if common.HeaderType(chdr.Type) == common.HeaderType_CONFIG {
			fmt.Printf("        type=[CONFIG]\n")
			if err = parseConfig(payload); err != nil {
				return err
			}
		} else if common.HeaderType(chdr.Type) == common.HeaderType_ENDORSER_TRANSACTION {
			fmt.Printf("        type=[ENDORSER_TRANSACTION]\n")
			if err = parseEndorserTransaction(payload); err != nil {
				return err
			}
		} else {
			fmt.Printf("        txid=[%s], channel=[%s], type=[UNKNOWN]\n", chdr.TxId, chdr.ChannelId)
		}
	}

	return nil
}

func parseEndorserTransaction(payload *common.Payload) error {
	var err error
	var tx *peer.Transaction
	if tx, err = utils.GetTransaction(payload.Data); err != nil {
		return err
	}

	fmt.Printf("        actions\n")
	for _, action := range tx.Actions {
		var capayload *peer.ChaincodeActionPayload
		var ca *peer.ChaincodeAction
		if capayload, ca, err = utils.GetPayloads(action); err != nil {
			return err
		}

		fmt.Printf("            endorsers\n")
		for _, endorser := range capayload.Action.Endorsements {
			var mspid, subject string
			if mspid, subject, err = decodeSerializedIdentity(endorser.Endorser); err != nil {
				return err
			}
			fmt.Printf("                endorser[%s:%s]\n", mspid, subject)
		}

		fmt.Printf("            RWSet\n")
		txRWSet := &rwsetutil.TxRwSet{}
		if err = txRWSet.FromProtoBytes(ca.Results); err != nil {
			return err
		}
		for _, nsRWSet := range txRWSet.NsRwSets {
			ns := nsRWSet.NameSpace
			if ns != "lscc" { // skip system chaincode
				fmt.Printf("                ns=[%v]\n", ns)
				fmt.Printf("                RDSet\n")
				for _, kvRead := range nsRWSet.KvRwSet.Reads {
					fmt.Printf("                     key=[%v]\n", kvRead.Key)
				}
				fmt.Printf("                WRSet\n")
				for _, kvWrite := range nsRWSet.KvRwSet.Writes {
					if kvWrite.IsDelete {
						fmt.Printf("                     key=[%v] op=[delete]\n", kvWrite.Key)
					} else {
						fmt.Printf("                     key=[%v] op=[write]\n", kvWrite.Key)
					}
				}
			}
		}
	}
	return nil
}
func parseConfig(payload *common.Payload) error {
	var err error

	var configEnvelope *common.ConfigEnvelope
	if configEnvelope, err = configtx.UnmarshalConfigEnvelope(payload.Data); err != nil {
		return err
	}

	var configGroup *common.ConfigGroup = configEnvelope.Config.ChannelGroup
	fmt.Printf("        Groups\n")
	for k, _ := range configGroup.Groups {
		fmt.Printf("            %s\n", k)
	}

	fmt.Printf("        Values\n")
	for k, v := range configGroup.Values {
		fmt.Printf("            %s\n", k)
		if k == "OrdererAddresses" {
			addresses := &common.OrdererAddresses{}
			if err = proto.Unmarshal(v.Value, addresses); err != nil {
				return err
			}
			for _, address := range addresses.Addresses {
				fmt.Printf("                [%s]\n", address)
			}
		}
	}
	return nil
}

func decodeSerializedIdentity(body []byte) (mspId, subj string, err error) {
	a := new(msp.SerializedIdentity)

	err = proto.Unmarshal(body, a)
	return a.Mspid, "", err
}
